/**
 ****************************************************************************
 * Copyright (C) Marcelo F. Ochoa. All rights reserved.                      *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included  with this distribution in *
 * the LICENSE file.                                                         *
 */

package org.apache.cocoon.acting;

import java.util.HashMap;
import java.util.Map;

import org.apache.avalon.excalibur.component.ComponentHandler;
import org.apache.avalon.excalibur.logger.LogKitManageable;
import org.apache.avalon.excalibur.logger.LogKitManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.components.sax.XMLByteStreamCompiler;
import org.apache.cocoon.components.sax.XMLByteStreamFragment;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.generation.DBPrismGenerator;
import org.apache.cocoon.xml.AbstractXMLConsumer;

/**
 * Allows actions to be written in DBrism. This allows to use DBPrism to produce
 * XML fragments that are later reused in generators.<br/>
 *
 * This action works in concert with the "DBPrism" generator, that offers
 * actions-related services such as redirect or result map access.
 *
 * As for generators, the DBPrism stored procedure is set using the "src" attribute.<br/>
 *
 * This action accepts a single parameter, "output-attribute", which names
 * the request attribute where the XSP-generated document will be stored
 * (as an <code>XMLizable</code>).
 *
 * When "output-attribute" is set, the action status defaults to "success",
 * meaning child sitemap statements are executed. This allows to use an
 * existing XSP without modification with this action.<br/>
 *
 * When "output-attribute" isn't set, the action status defaults to "failure".
 * The XSP must then use the "action" logicsheet to set its status.<br/>
 *
 * Example :
 * <pre>
 *   &lt;action type="dbprism-action" src="/xmlj/pkg.proc"&gt;
 *     &lt;map:param name="output-attribute" value="dbprism-action-result"/&gt;
 *     ...
 *   &lt;/action&gt;
 * </pre>
 *
 * @author <a href="mailto:mochoa@ieee.org">Marcelo F. Ochoa</a>
 * @version CVS $Id: DBPrismAction.java,v 1.1 2003/11/09 11:33:18 mochoa Exp $
 */
public class DBPrismAction
        extends ConfigurableComposerAction
        implements Contextualizable, Disposable, ThreadSafe, LogKitManageable {

    public static final String REDIRECTOR_OBJECT = "dbprism-action:redirector";
    public static final String ACTION_RESULT_OBJECT = "dbprism-action:result";
    public static final String ACTION_SUCCESS_OBJECT = "dbprism-action:success";
    public static final String OUTPUT_ATTRIBUTE = "output-attribute";

    private LogKitManager logKitManager;
    private Context context = null;

    ComponentHandler generatorHandler;

    /**
      * Contextualize this class. Stores context information to be use by configure method in order to resolve
      * /prism.xconf as $WEBAPP/WEB_INF/prism.xconf in servlet 2.2+ containers
      */
    public void contextualize(org.apache.avalon.framework.context.Context ctx) throws ContextException {
       //System.out.println("contextualize called id="+this.toString());
       if (ctx==null)
         throw new ContextException("DBPrismAction requires context object.");
       else
         this.context = ctx;
   }


    public void configure(Configuration conf)
      throws ConfigurationException {
        try {
            this.generatorHandler = ComponentHandler.getComponentHandler(
                DBPrismGenerator.class,
                conf,
                this.manager,
                this.context, // Context
                null,  // RoleManager
                null  // LogKitManager
            );
            this.generatorHandler.setLogger(getLogger());
            this.generatorHandler.initialize();

        } catch(Exception e) {
            throw new ConfigurationException("Cannot set up component handler", e);
        }
    }

    public void setLogKitManager(LogKitManager logKitManager) {
        this.logKitManager = logKitManager;
    }

    public void dispose() {
        if (this.generatorHandler != null) {
            this.generatorHandler.dispose();
            this.generatorHandler = null;
        }
        this.context = null;
    }

    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel,
        String source, Parameters parameters)
      throws Exception {

        getLogger().debug("DBPrismAction source: " + source);

        String outputKey = parameters.getParameter(OUTPUT_ATTRIBUTE, null);
        Map resultMap = new HashMap();
        Object success = null;

        // Get a DBPrismGenerator
        DBPrismGenerator generator = (DBPrismGenerator)this.generatorHandler.get();

        // Generator ouptut, if output-attribute was given
        XMLByteStreamCompiler compiler = null;

        try {
            generator.setLogger(getLogger());
            generator.compose(this.manager);
            generator.setup(resolver, objectModel, source, parameters);

            // Setup generator output
            if (outputKey == null) {
                // discard output to a "black hole"
                generator.setConsumer(new AbstractXMLConsumer() { } ); // Make the abstract class instanciable
            } else {
                // store output in a byte stream
                compiler = new XMLByteStreamCompiler();
                generator.setConsumer(compiler);
            }

            // Augment the object model for the DBPrism X-Cocoon-* header
            objectModel.put(REDIRECTOR_OBJECT, redirector);
            objectModel.put(ACTION_RESULT_OBJECT, resultMap);

            // Let the DBPrism do it's stuff
            generator.generate();
            success = objectModel.get(ACTION_SUCCESS_OBJECT);

        } catch (Exception e) {
            getLogger().warn("DBPrismAction error", e);
            throw e;
        } finally {
            // Release generator
            generatorHandler.put(generator);

            // Clean up object model
            objectModel.remove(REDIRECTOR_OBJECT);
            objectModel.remove(ACTION_RESULT_OBJECT);
            objectModel.remove(ACTION_SUCCESS_OBJECT);
        }
        if (outputKey != null) {
            // Success defaults to true when the whole output is captured
            if (success == null) {
                success = Boolean.TRUE;
            }
            if (success == Boolean.TRUE) {
                // Store the DBPrism output in the request
                Request req = ObjectModelHelper.getRequest(objectModel);
                req.setAttribute(outputKey, new XMLByteStreamFragment(compiler.getSAXFragment()));
            }
        }
        // If success, return DBPrism generated sitemap components
        return (success == Boolean.TRUE) ? resultMap : null;
    }
}
